const isObject = (val) => Object.prototype.toString.call(val) === '[object Object]';
const isArray = (val) => Array.isArray(val);
const isFunction = (val) => typeof val === 'function';
const INTERNAL = Symbol('INTERNAL');


export function produce(baseState, producer) {
    const proxy = toProxy(baseState);
    producer(proxy);
    const internal = proxy[INTERNAL];
    return internal.mutated ? internal.draftState : baseState;
}

/**
 * 对象浅拷贝
 */
function createDraftState(baseState) {
  if (isArray(baseState)) {
      return [...baseState];
  } else if (isObject(baseState)) {
      return Object.assign({}, baseState);
  } else {
      return baseState;
  }
};

export function toProxy(baseState, valueChange) {
    const keyToProxy = {};

    const internal = {
        draftState: createDraftState(baseState),
        keyToProxy,
        mutated: false // 此对象是否发生变更
    }

    return new Proxy(baseState, {
        get(target, key) {
            if (key === INTERNAL) {
                return internal;
            }

            const value = target[key];

            if (isObject(value) || isArray(value)) {
                if (key in keyToProxy) {
                    return keyToProxy[key];
                } else {
                    keyToProxy[key] = toProxy(value, () => {
                        internal.mutated = true;

                        const proxyOfChild = keyToProxy[key];
                        const { draftState } = proxyOfChild[INTERNAL];

                        internal.draftState[key] = draftState;
                        valueChange && valueChange();
                    });

                    return keyToProxy[key];
                }
            } else if (isFunction(value)) {
                internal.mutated = true;
                valueChange && valueChange();
                return value.bind(internal.draftState);
            }

            return internal.mutated ? internal.draftState[key] : baseState[key];
        },

        set(target, key, value) {
            internal.mutated = true;

            const { draftState } = internal;

            for (const key in target) {
                draftState[key] = key in draftState ? draftState[key] : target[key];
            }

            draftState[key] = value;

            valueChange && valueChange();

            return true;
        }
    });
}